#ifndef STRING_UTIL_H
#define STRING_UTIL_H

#include <string>
#include <string.h>
#include <algorithm>
#include <vector>
#include <memory>

namespace sh
{
/**
 * @brief:  是否区分大小写
 * @author: sherlock_lht
 */
enum CaseSensitivity
{
    CaseInsensitive = 0,//不区分大小写
    CaseSensitive       //区分大小写
};

/**
 * @brief:  分割字符串的行为定义
 * @author: sherlock_lht
 */
enum SplitBehavior
{
    KeepEmptyParts = 0, //如果字段是空的，也放在结果里
    SkipEmptyParts      //如果字段是空的，不放在结果里
};
/**
 * 常用字符串工具，基于std::string，用于ASCII字符串处理
 * 接口参考Qt的接口风格
 * 使用时直接#include该文件即可，不需要加入项目中
 *
 * @author: sherlock_lht
 */
class StringUtil
{
public:
    /**
     * format方法来自github，支持C语言风格的字符串格式化
     */
    template<typename ... Args>
    static std::string format(const char *format, Args ... args)
    {
        auto size_buf = std::snprintf(nullptr, 0, format, args ...) + 1;
        std::unique_ptr<char[]> buf(new(std::nothrow) char[size_buf]);

        if (!buf)
            return std::string("");

        std::snprintf(buf.get(), size_buf, format, args ...);
        return std::string(buf.get(), buf.get() + size_buf - 1);
    }

    /**
     * Returns a lowercase copy of the string.
     */
    static std::string toLower(const std::string &origin_str)
    {
        std::string result = origin_str;
        std::transform(result.begin(), result.end(), result.begin(), ::tolower);
        return result;
    }

    /**
     * Returns an uppercase copy of the string.
     */
    static std::string toUpper(const std::string &origin_str)
    {
        std::string result = origin_str;
        std::transform(result.begin(), result.end(), result.begin(), ::toupper);
        return result;
    }

    /**
     * Returns true if the string starts with s; otherwise returns false.
     * If cs is sh::CaseSensitive (default), the search is case sensitive;
     * otherwise the search is case insensitive.
     */
    static bool startsWith(const std::string &origin_str,
                           const std::string &substr,
                           CaseSensitivity cs = CaseSensitivity::CaseSensitive)
    {
        if (CaseSensitivity::CaseSensitive == cs)
        {
            return origin_str.find(substr) == 0;
        }
        return toLower(origin_str).find(toLower(substr)) == 0;
    }

    /**
     * Returns true if the string ends with s; otherwise returns false.
     * If cs is sh::CaseSensitive (default), the search is case sensitive;
     * otherwise the search is case insensitive.
     */
    static bool endsWith(const std::string &origin_str,
                         const std::string &substr,
                         CaseSensitivity cs = CaseSensitivity::CaseSensitive)
    {
        std::size_t last_index = origin_str.length() - substr.length();
        if (CaseSensitivity::CaseSensitive == cs)
        {
            return origin_str.rfind(substr) == last_index;
        }
        return toLower(origin_str).rfind(toLower(substr)) == last_index;
    }

    /**
     * Compares s1 with s2 and returns an integer less than, equal to,
     * or greater than zero if s1 is less than, equal to, or greater than s2.
     *
     * If cs is sh::CaseSensitive, the comparison is case sensitive; otherwise the comparison is case insensitive.
     */
    static int compare(const std::string &s1, const std::string &s2,
                       CaseSensitivity cs = CaseSensitivity::CaseSensitive)
    {
        if (CaseSensitivity::CaseSensitive == cs)
        {
            return strcmp(s1.c_str(), s2.c_str());
        }
        return strcmp(toLower(s1).c_str(), toLower(s2).c_str());
    }

    /**
     * Returns true if this string contains an occurrence of the string str;
     * otherwise returns false.
     *
     * If cs is sh::CaseSensitive (default), the search is case sensitive;
     * otherwise the search is case insensitive.
     */
    static bool contains(const std::string &str, const std::string &substr,
                         CaseSensitivity cs = CaseSensitivity::CaseSensitive)
    {
        if (CaseSensitivity::CaseSensitive == cs)
        {
            return str.find(substr) != std::string::npos;
        }
        return toLower(substr).find(toLower(substr)) != std::string::npos;
    }

    /**
     * Splits the string into substrings wherever sep occurs, and returns the list of those strings.
     * If sep does not match anywhere in the string, split() returns a single-element list containing this string.
     * cs specifies whether sep should be matched case sensitively or case insensitively.
     * If behavior is sh::SkipEmptyParts, empty entries don't appear in the result.
     * By default, empty entries are kept.
     *
     */
    static std::vector<std::string> split(const std::string &str, const std::string &sep,
                                          SplitBehavior behavior = SplitBehavior::KeepEmptyParts,
                                          CaseSensitivity cs = CaseSensitivity::CaseSensitive)
    {
        std::string check_str = CaseSensitivity::CaseSensitive == cs ? str : toLower(str);
        std::string check_sep = CaseSensitivity::CaseSensitive == cs ? sep : toLower(sep);

        size_t length = check_sep.length();
        std::size_t index = check_str.find(check_sep);
        std::size_t start_index = 0;

        std::vector<std::string> result;
        while (true)
        {
            std::string element = str.substr(start_index, index - start_index);
            if (!element.empty() || (element.empty() && SplitBehavior::KeepEmptyParts == behavior))
            {
                result.emplace_back(element);
            }
            if (std::string::npos == index)
            {
                break;
            }
            start_index = index + length;
            index = check_str.find(check_sep, start_index);
        }
        return result;
    }

    /**
     * Returns a string that has token removed from the start.
     */
    static std::string trimLeft(const std::string &str, const std::string &token = " ")
    {
        std::string result = str;
        result.erase(0, result.find_first_not_of(token));
        return result;
    }

    /**
     * Returns a string that has token removed from the end.
     */
    static std::string trimRight(const std::string &str, const std::string &token = " ")
    {
        std::string result = str;
        result.erase(result.find_last_not_of(token) + 1);
        return result;
    }

    /**
     * Returns a string that has token removed from the start and end.
     */
    static std::string trim(const std::string &str, const std::string &token = " ")
    {
        std::string result = str;
        result.erase(0, result.find_first_not_of(token));
        result.erase(result.find_last_not_of(token) + 1);
        return result;
    }

    /**
     * Returns a string that has whitespace removed from the start and the end.
     * This includes the ASCII characters '\t', '\n', '\v', '\f', '\r', and ' '.
     */
    static std::string trimmed(const std::string &str)
    {
        std::string result = str;
        std::string token = "\t\n\v\f\r ";
        result.erase(0, result.find_first_not_of(token));
        result.erase(result.find_last_not_of(token) + 1);
        return result;
    }

    /**
     * Replaces n characters beginning at index position with the string after and returns a reference to this string.
     */
    static std::string replace(const std::string &str, const std::string &src, const std::string &dest)
    {
        std::string result;
        std::string::size_type pos_begin = 0;
        std::string::size_type pos = str.find(src);
        while (std::string::npos != pos)
        {
            result.append(str, pos_begin, pos - pos_begin);
            result.append(dest);
            pos_begin = pos + src.length();
            pos = str.find(src, pos_begin);
        }
        if (pos_begin < str.length())
        {
            result.append(str.begin() + pos_begin, str.end());
        }

        return result;
    }
};
}

#endif//STRING_UTIL_H
